﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SimpleOrmCFPC.upgrade.core;

namespace SimpleOrmCFPC.upgrade
{
    internal class Upgrader : IUpgrader
    {
        private TableFactory tablefactory;
        private DBUpgrader dbUpgrader;
        private TableDialect tableDialect;
        private ISchemaProvider schemaProvider;

        public Upgrader(TableFactory tablefactory, DBUpgrader dbUpgrader, TableDialect tableDialect, ISchemaProvider schemaProvider)
        {
            this.tablefactory = tablefactory;
            this.schemaProvider = schemaProvider;
            this.tableDialect = tableDialect;
            this.dbUpgrader = dbUpgrader;
        }

        private IUpgrader SqlRequest(String request)
        {
            dbUpgrader.Upgrade(request);
            return this;
        }

        public IUpgrader Table<T>()
        {
            var tableName = typeof (T).Name;
            if (!schemaProvider.TableExists(tableName))
            {
                var createTableRequest = tablefactory.ComputeRequestForCreateTable<T>();
                return SqlRequest(createTableRequest);
            }
            else
            {
                var newTableSchema = CreateTableSchema<T>();
                var currentTableMapping = schemaProvider.GetTableSchema(tableName);

                var columnNamesToCreate = newTableSchema.Columns.Select(c => c.Name).Except(currentTableMapping.Columns.Select(c => c.Name));
                var columnNamesToDelete = currentTableMapping.Columns.Select(c => c.Name).Except(newTableSchema.Columns.Select(c => c.Name));
                DeleteColumns(columnNamesToDelete);
                CreateColumns(columnNamesToCreate, newTableSchema);

                currentTableMapping = schemaProvider.GetTableSchema(tableName);
                var columnsToModify = newTableSchema.Columns.Except(currentTableMapping.Columns);
                UpdateColumns(columnsToModify);
            }
            return this;
        }

        private void UpdateColumns(IEnumerable<ColumnMapping> columnsToModify)
        {
            
        }

        private void CreateColumns(IEnumerable<string> columnNamesToCreate, TableMapping newTableSchema)
        {
            foreach (var columnName in columnNamesToCreate)
            {
                var columnToAdd = (from c in newTableSchema.Columns where c.Name == columnName select c).First();
                var alterTableRequest = string.Format("ALTER TABLE {0} ADD {1} {2}", newTableSchema.Name, columnToAdd.Name, tableDialect.ConvertType(columnToAdd.Type, columnToAdd.MaxLength));
                SqlRequest(alterTableRequest);
            }
        }

        private void DeleteColumns(IEnumerable<string> columnNamesToDelete)
        {
            
        }

        private TableMapping CreateTableSchema<T>()
        {
            TableMappingFactory mappingFactory = new TableMappingFactory(new ColumnMappingFactory(p => SQLTypeFactory.ConvertToDbType(p.PropertyType)));
            return mappingFactory.Create<T>();
        }
    }

    public interface IColumnMappingFactory
    {
        ColumnMapping Create(PropertyInfo property);
    }


    public class TableMappingFactory
    {
        private IColumnMappingFactory columnMappingFactory;

        public TableMappingFactory(IColumnMappingFactory columnMappingFactory)
        {
            this.columnMappingFactory = columnMappingFactory;
        }


        public TableMapping Create<T>()
        {
            var tableMapping = new TableMapping {Name = typeof (T).Name};
            foreach (var property in typeof (T).GetProperties())
                tableMapping.Columns.Add(columnMappingFactory.Create(property));
            return tableMapping;
        }
    }

    public class TableMapping
    {
        public TableMapping()
        {
            Columns = new List<ColumnMapping>();
        }

        public String Name { get; set; }

        public List<ColumnMapping> Columns { get; set; }
    }

    public class ColumnMapping
    {
        public ColumnMapping(string name, bool primaryKey, bool notNull, bool autoIncrement, DbType type, int maxLength)
        {
            Name = name;
            PrimaryKey = primaryKey;
            NotNull = notNull;
            AutoIncrement = autoIncrement;
            Type = type;
            MaxLength = maxLength;
        }

        public string Name { get; set; }

        public bool PrimaryKey { get; set; }

        public bool NotNull { get; set; }

        public bool AutoIncrement { get; set; }

        public DbType Type { get; set; }

        public int MaxLength { get; set; }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (ColumnMapping)) return false;
            return Equals((ColumnMapping) obj);
        }

        public bool Equals(ColumnMapping other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Name, Name) && other.PrimaryKey.Equals(PrimaryKey) && other.NotNull.Equals(NotNull) && other.AutoIncrement.Equals(AutoIncrement) && Equals(other.Type, Type) && other.MaxLength == MaxLength;
        }
    }



    public class CreateColumn<TTable> : ICreateColumn<TTable>
    {
        private readonly IUpgrader upgrader;
        private readonly TableDialect tableDialect;

        public CreateColumn(IUpgrader upgrader, TableDialect tableDialect)
        {
            this.upgrader = upgrader;
            this.tableDialect = tableDialect;
        }

        public class CreateColumnWithType<TTable, TData> : ICreateColumnWithType<TTable, TData>
        {
            private readonly IUpgrader upgrader;
            private readonly TableDialect tableDialect;
            private readonly int length;

            public CreateColumnWithType(IUpgrader upgrader, TableDialect tableDialect, int length)
            {
                this.upgrader = upgrader;
                this.tableDialect = tableDialect;
                this.length = length;
            }

            public void Named(string columnName)
            {
                string sqlType = tableDialect.ConvertType(TypeConverter.Convert(typeof(TData)), length);
                //upgrader.SqlRequest(String.Format("ALTER TABLE {0} ADD {1} {2}", typeof(TTable).Name, columnName, sqlType));
            }
        }

        public ICreateColumnWithType<TTable, TData> OfType<TData>(int length)
        {
            return new CreateColumnWithType<TTable, TData>(upgrader, tableDialect, length);
        }

        public ICreateColumnWithType<TTable, TData> OfType<TData>()
        {
            return OfType<TData>(0);
        }
    }
}